package org.transplanner;

import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.HillHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import org.rtscam.RtsCam;

/**
 * test
 *
 * @author Ivosh
 */
/**
 * Sample 10 - How to create fast-rendering terrains from heightmaps, and how to
 * use texture splatting to make the terrain look good.
 */
public class Main extends SimpleApplication {

    private TerrainQuad terrain;
    Material mat_terrain;

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }
    private ActionListener actionListener = new ActionListener() {
        @Override
        public void onAction(String name, boolean isPressed, float tpf) {
            switch (name) {
                case "LClick":
                    Vector2f click2d = inputManager.getCursorPosition();
                    Vector3f click3d = cam.getWorldCoordinates(
                            new Vector2f(click2d.x, click2d.y), 0f).clone();
                    Vector3f dir = cam.getWorldCoordinates(
                            new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d).normalizeLocal();
                    Ray ray = new Ray(click3d, dir);
                    findBounding(ray, name);
                    break;
                default:
                    break;
            }
        }
    };
    private AnalogListener analogListener = new AnalogListener() {
        @Override
        public void onAnalog(String name, float value, float tpf) {
            switch (name) {
                case "Left":
                    cam.setLocation(cam.getLocation().add(cam.getLeft()));
                    break;
                case "Right":
                    cam.setLocation(cam.getLocation().subtract(cam.getLeft()));
                    break;
                case "Up":
                    cam.setLocation(cam.getLocation().add(cam.getUp()));
                    break;
                case "Down":
                    cam.setLocation(cam.getLocation().subtract(cam.getUp()));
                    break;
                case "ZoomIn":
                    cam.setLocation(cam.getLocation().add(cam.getDirection().mult(50)));
                    break;
                case "ZoomOut":
                    cam.setLocation(cam.getLocation().subtract(cam.getDirection().mult(50)));
                    break;
                default:
                    break;
            }
            cam.update();
        }
    };

    private void initKeys() {
        inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A),
                new KeyTrigger(KeyInput.KEY_LEFT));
        inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D),
                new KeyTrigger(KeyInput.KEY_RIGHT));
        inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W),
                new KeyTrigger(KeyInput.KEY_UP));
        inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S),
                new KeyTrigger(KeyInput.KEY_DOWN));
        inputManager.addMapping("ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
        inputManager.addMapping("ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
        inputManager.addMapping("LClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(analogListener, new String[]{"Left", "Right", "Up", "Down", "ZoomIn", "ZoomOut"});
        inputManager.addListener(actionListener, new String[]{"LClick"});
    }

    private void initCam() {
        cam.setLocation(new Vector3f(0, 400, 0));
        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
    }

    @Override
    public void simpleInitApp() {
        flyCam.setEnabled(false);
        final RtsCam rtsCam = new RtsCam(cam, rootNode);
        rtsCam.registerWithInput(inputManager);
        rtsCam.setCenter(new Vector3f(20, 20, 20));
        //initKeys();
        //initCam();

        /**
         * 1. Create terrain material and load four textures into it.
         */
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");


        /**
         * 1.2) Add GRASS texture into the red layer (Tex1).
         */
        Texture grass = assetManager.loadTexture(
                "Textures/grass.jpg");
        grass.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex1", grass);
        mat_terrain.setFloat("Tex1Scale", 32f);

        HillHeightMap heightmap = null;
        HillHeightMap.NORMALIZE_RANGE = 100; // optional
        try {
            heightmap = new HillHeightMap(513, 10000, 50, 100, (byte) 3); // byte 3 is a random seed
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        int patchSize = 65;
        terrain = new TerrainQuad("GameMap", patchSize, 513, heightmap.getHeightMap());

        /**
         * 4. We give the terrain its material, position & scale it, and attach
         * it.
         */
        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, -100, 0);
        terrain.setLocalScale(2f, 1f, 2f);
        rootNode.attachChild(terrain);

        /**
         * 5. The LOD (level of detail) depends on were the camera is:
         */
        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
        terrain.addControl(control);
    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    /**
     * Find the intersection of the calling object and the object with given
     * name in the direction defined by the given Ray. If the returned value is
     * (0, 0, 0), that means it did not find any results.
     *
     * @param ray The ray defines the direction of searching.
     * @param targetName The name of the model which is being looked for.
     * @return A Vector3f which defines the intersection point.
     */
    protected Vector3f findBounding(Ray ray, String targetName) {
        Vector3f nearest = new Vector3f();
        float nearestDist = Float.MAX_VALUE;
        CollisionResults results = new CollisionResults();
        results.clear();
        terrain.collideWith(ray, results);
        System.out.println("----- Collisions? " + results.size() + "-----");
        for (int i = 0; i < results.size(); i++) {
            // For each hit, we know distance, impact point, name of geometry.
            float dist = results.getCollision(i).getDistance();
            Vector3f pt = results.getCollision(i).getContactPoint();
            if (dist < nearestDist) {
                nearestDist = dist;
                nearest = pt;
            }
            System.out.println("* Collision #" + i);
            System.out.println("  You clicked at " + pt + ", " + dist + " wu away.");
        }
        if (results.size() < 0) {
            return Vector3f.NAN;
        }
        return nearest;
    }
}
